// @author U099151W Sun Chenchen
// all functions in this file  are implemented by the author unless specifically indicated
var NPC = function() {
    
    var _x;
    var _y;
    this.distanceTo = function(NPC2) {
        var NPC1Position = toWorldCoord(_grid.getIndexOfGrid().colIndex, _grid.getIndexOfGrid().rowIndex, _x, _y);
        var NPC2Position = toWorldCoord(NPC2.getGrid().getIndexOfGrid().colIndex, NPC2.getGrid().getIndexOfGrid().rowIndex,
        NPC2.getPosition().x, NPC2.getPosition().y);
        var x = Math.abs(NPC1Position.x - NPC2Position.x);
        var y = Math.abs(NPC1Position.y - NPC2Position.y);
        return Math.floor(Math.sqrt(x*x+y*y));
    };

    var _type;
    var _reachDestination=false;
    var _inRandomMove;
    var _randomCount = 0;
    var _randomMax = 10;
    this.eventOutcomeQueue = [];
    var _walkDuration = 900;
    this.setWalkDuration = function(duration) {
        _walkDuration = duration;
    };

    this.reachDestination  = function() {
        return _reachDestination;
    };

    this.setreachDestination = function(dest){
        _reachDestination = dest;
    };

    var _radius = 2;
    this.getRadius = function() {
        return _radius;
    };
    this.inRadius = function(NPC2) {
        var grid2 = NPC2.getGrid();
        var NPC2WorldCoord = toWorldCoord(grid2.getIndexOfGrid().colIndex, grid2.getIndexOfGrid().rowIndex,
        NPC2.getPosition().x, NPC2.getPosition().y);
        var NPC1WorldCoord = toWorldCoord(_grid.getIndexOfGrid().colIndex, _grid.getIndexOfGrid().rowIndex,
        _x, _y);
        var diffX = Math.abs(NPC2WorldCoord.x - NPC1WorldCoord.x);
        var diffY = Math.abs(NPC2WorldCoord.y - NPC1WorldCoord.y);
        if (diffX + diffY > _radius) {
            return false;
        } else {
            return true;
        }

    };

    var _insideBuilding = false;
    this.setInsideBuilding = function(inside) {
        _insideBuilding = inside;
    };
    this.insideBuilding = function () {
        return _insideBuilding;
    };

    var _destination;

    this.eventType = "default";
    this.start_event = false;
    this.meetup = 0;
    this.finished = 0;
    this.player_timer = 0;

    this.updateSprite = function() {

    };

    this.getType = function() {
        return _type;
    };

    var _id;
    this.getId = function() {
        return _id;
    };
    this.setId = function(id) {
        _id = id;
    };

    var _grid;
    this.getGrid = function() {
        return _grid;
    };

    var _building;
    this.getBuilding = function() {
        return _building;
    };
    this.setBuilding = function(building) {
        _building = building;
    };

    var _facing;
    this.get_facing = function(){
        return _facing;
    };
    this.changeFacing = function(direction) {
        _facing = direction.charAt(0).toUpperCase() + direction.substr(1, direction.length-1);
    };
    var _sprite;
    var _spriteFrame = 0;
    var _spriteLength = 1;
    var _spriteFrameDuration = 0;
    var _spriteFrameStart = Date.now();

    var _actionDuration = 300;
    this.setActionDuration = function(actionDuration) {
        _actionDuration = actionDuration;
    };
    var _actionStart = Date.now();
    var _inAction = false;

    this.getPosition = function() {
        return {
            x : _x,
            y : _y
        };
    };
    this.setPosition = function(x, y) {
        _x = x;
        _y = y;
    };

    this.randomMove = function() {
        if (!_inAction) {
            if (!_inRandomMove) {
                _randomCount = 0;
                _inRandomMove = true;
                if (!_insideBuilding) {
                    var gridList = _grid.getMap().getCurrentGrids();
                    var gridChosen = gridList[Math.floor(Math.random()*gridList.length)];
                    var chosenObstacleMap = gridChosen.getObstacleMap();
                    var i = Math.floor(Math.random()*_grid.getGridSize().row),
                        j = Math.floor(Math.random()*_grid.getGridSize().col);
                    while(chosenObstacleMap[i][j] === 1) {
                        i = Math.floor(Math.random()*_grid.getGridSize().row);
                        j = Math.floor(Math.random()*_grid.getGridSize().col);
                        if (chosenObstacleMap[i][j] === 2) {
                            console.log("Random Move : Enter Building");
                        }
                    }
                    _destination = {
                        x : j,
                        y : i,
                        gridX : gridChosen.getIndexOfGrid().colIndex,
                        gridY : gridChosen.getIndexOfGrid().rowIndex
                    };
                } else {
                    var chosenObstacleMap = _building.getObstacleMap();
                    var i = Math.floor(Math.random()*_building.getGridSize().row),
                        j = Math.floor(Math.random()*_building.getGridSize().col);
                    while(chosenObstacleMap[i][j] === 1) {
                        i = Math.floor(Math.random()*_building.getGridSize().row);
                        j = Math.floor(Math.random()*_building.getGridSize().col);
                        if (chosenObstacleMap[i][j] === 2) {
                            console.log("Random Move : Leave Building");
                        }
                    }
                    _destination = {
                        x : j,
                        y : i,
                        gridX : 0,
                        gridY : 0
                    }
                }
            } else {
                if (!_insideBuilding) {
                    if (_randomCount > _randomMax) {
                        _randomCount = 0;
                        _inRandomMove = false;
                    }
                    _randomCount++;
                    var start = _grid.getIndexOfGrid();
                    if (start.colIndex === _destination.gridX &&
                        start.rowIndex === _destination.gridY &&
                        _x === _destination.x &&
                        _y === _destination.y) {
                        _inRandomMove = false;
                    }
                    var minX = start.colIndex < _destination.gridX ? start.colIndex : _destination.gridX;
                    var maxX = start.colIndex > _destination.gridX ? start.colIndex : _destination.gridX;
                    var minY = start.rowIndex < _destination.gridY ? start.rowIndex : _destination.gridY;
                    var maxY = start.rowIndex > _destination.gridY ? start.rowIndex : _destination.gridY;

                    this.walkTo(_destination.gridX, _destination.gridY, _destination.x, _destination.y,
                        _grid.getMap().getObstacleMap(minX, minY, maxX, maxY), minX, minY);
                } else {
                    if (_x === _destination.x &&
                        _y === _destination.y) {
                        _inRandomMove = false;
                    }

                    this.walkTo(0, 0, _destination.x, _destination.y,
                        _building.getObstacleMap(), 0, 0);
                }

            }
        }
    };

    this.walkTo = function(gridX, gridY, x, y, obstacleMap, mapStartGridX, mapStartGridY) {
        if (_inAction) {
            return;
        }
        _destination = {
            gridX : gridX,
            gridY : gridY,
            x : x,
            y : y
        };
        var start;
        var destination;
        if (!_insideBuilding) {
            start = toWorldCoord(_grid.getIndexOfGrid().colIndex - mapStartGridX, _grid.getIndexOfGrid().rowIndex - mapStartGridY,
                _x, _y);
            destination = toWorldCoord(gridX - mapStartGridX, gridY - mapStartGridY, x, y);
        } else {
            start = {
                x : _x,
                y : _y
            };
            destination = {
                x : x,
                y : y
            };
        }

        if (start.x === destination.x && start.y === destination.y) {
            _reachDestination = true;
        } else {
            var _path = a_star([start.x, start.y], [destination.x, destination.y],
                    obstacleMap, obstacleMap[0].length, obstacleMap.length, 0);
            if (_path.length === 0) {
                console.log("no path");
                return;
            }
            var nextGrid = _path[1];
            if (_path[1].x > start.x) {
                this.walk("Right");
            } else if (_path[1].x < start.x) {
                this.walk("Left");
            } else if (_path[1].y > start.y) {
                this.walk("Down");
            } else if (_path[1].y < start.y) {
                this.walk("Up");
            }
        }
    };

    function toWorldCoord(gridX, gridY, x, y) {
        var gridSize = _grid.getGridSize();

        var wx = gridX * gridSize.col + x;
        var wy = gridY * gridSize.row + y;
        return {
            x : wx,
            y : wy
        };
    }

    function toLocalCoord(x, y) {
        var gridSize = _grid.getGridSize();
        var gridX = Math.floor(x / gridSize.col);
        var gridY = Math.floor(y / gridSize.row);
        var lx = x % gridSize.col;
        var ly = y % gridSize.row;
        return {
            gridX : gridX,
            gridY : gridY,
            x : lx,
            y : ly
        };
    }

    function onWalkEnd(that, direction, callback) {
        if (_facing === direction) {
            _inAction = false;
            // check collision
            var tmpX = _x, tmpY = _y;
            switch(direction) {
                case "Up" :
                    tmpY -= 1;
                    break;
                case "Down" :
                    tmpY += 1;
                    break;
                case "Left" :
                    tmpX -= 1;
                    break;
                case "Right" :
                    tmpX += 1;
                    break;
                default :
                    break;
            }
            // check obstacles
            onActionEnd(this);
            if (!_insideBuilding) {
                var _gridPosition = _grid.getIndexOfGrid();
                var worldCoord = toWorldCoord(_gridPosition.colIndex, _gridPosition.rowIndex, tmpX, tmpY);
                worldCoord.x = worldCoord.x >= 0 ? worldCoord.x : 0;
                worldCoord.y = worldCoord.y >= 0 ? worldCoord.y : 0;
                var localCoord = toLocalCoord(worldCoord.x, worldCoord.y);

                var obstacleMap = _grid.getMap().getObstacleMap(localCoord.gridX, localCoord.gridY,
                    localCoord.gridX, localCoord.gridY);
                if (obstacleMap[localCoord.y][localCoord.x] === 2) {
                    var newGrid = _grid.getMap().getGridById(localCoord.gridY, localCoord.gridX);
                    var buildiing = newGrid.getPortalByPosition(localCoord.x, localCoord.y);
                    if(buildiing) {
                        var buildingObstacles = buildiing.getObstacleMap();
                        var buildingPortals = buildiing.getPortals();
                        for (var k = 0; k < buildingPortals.length; k++) {
                            if (buildingObstacles[buildingPortals[k].y][buildingPortals[k].x] === 2) {
                                // enterable
                                _grid.removeNPC(that);
                                _grid = null;
                                buildiing.addNPC(that);
                                _x = buildingPortals[k].x;
                                _y = buildingPortals[k].y;
                                _building = buildiing;
                                _insideBuilding = true;
                                _inRandomMove = false;
                                return;
                            }
                        }
                    }
                }

                if (obstacleMap[localCoord.y][localCoord.x] === 0) {
                    _x = tmpX;
                    _y = tmpY;
                }

                var gridIndex = _grid.getIndexOfGrid();
                var worldCoord = toWorldCoord(gridIndex.colIndex, gridIndex.rowIndex, _x, _y);
                worldCoord.x = worldCoord.x >= 0 ? worldCoord.x : 0;
                worldCoord.y = worldCoord.y >= 0 ? worldCoord.y : 0;
                if (callback) {
                    callback(worldCoord.x, worldCoord.y);
                }

                var localCoord = toLocalCoord(worldCoord.x, worldCoord.y);
                _x = localCoord.x;
                _y = localCoord.y;
                if (localCoord.gridX !== gridIndex.colIndex ||
                    localCoord.gridY !== gridIndex.rowIndex) {
                    // remove from the old one
                    _grid.removeNPC(that);
                    _x = localCoord.x;
                    _y = localCoord.y;
                    var newGrid = _grid.getMap().getGridById(localCoord.gridY, localCoord.gridX);
                    newGrid.addNPC(that);
                    _grid = newGrid;

                }
            } else {
                // inside building
                var localCoord = {
                    x : tmpX,
                    y : tmpY
                };
                var obstacleMap = _building.getObstacleMap();
                if (localCoord.x < 0 ) {
                    localCoord.x = 0;
                }
                if (localCoord.x >= _building.getGridSize().col ) {
                    localCoord.x = _building.getGridSize().col-1;
                }
                if (localCoord.y < 0 ) {
                    localCoord.y = 0;
                }
                if (localCoord.y >= _building.getGridSize().row ) {
                    localCoord.y = _building.getGridSize().row-1;
                }
                if (obstacleMap[localCoord.y][localCoord.x] === 0) {
                    _x = localCoord.x;
                    _y = localCoord.y;
                }
                if (obstacleMap[localCoord.y][localCoord.x] === 2) {
                    var opposite = _building.getOpposite();
                    var grid = opposite.grid;
                    if(grid) {
                        var gridObstacles = grid.getObstacleMap();
                        var gridPortals = opposite.portals;
                        for (var k = 0; k < gridPortals.length; k++) {
                            if (gridObstacles[gridPortals[k].y+1][gridPortals[k].x] === 0) {
                                // enterable
                                _building.removeNPC(that);
                                _building  = null;
                                grid.addNPC(that);
                                _grid = grid;
                                _x = gridPortals[k].x;
                                _y = gridPortals[k].y+1;
                                _insideBuilding = false;
                                _inRandomMove = false;
                                return;
                            }
                        }
                    }
                }

            }
        }
    }

    this.walk = function(direction, callback) {
        if (_inAction) {
            return;
        }

        this.setState({
            facing : direction,
            action : "Walk",
            length : 2,
            actionDuration : _walkDuration
        });
        var that = this;
        setTimeout(function() {
            onWalkEnd(that, direction, callback);
        },
            _actionDuration);
        _actionStart = Date.now();
    };

    this.setState = function(state) {
        if (state.facing) {
            _facing = state.facing;
        }
        _sprite = state.action;
        _spriteFrame = 0;
        _spriteLength = state.length;
        if (_spriteLength > 1) {
            var spriteName = _type.toLowerCase() + _sprite + _facing + (_spriteFrame+1);
            _spriteFrameDuration = spritesV1[spriteName].duration;
            _spriteFrameStart = Date.now();
        }
        if (state.actionDuration) {
            _actionDuration = state.actionDuration;
        } else {
            _actionDuration = 2000;
        }
        _actionStart = Date.now();
        _inAction = true;
    };

    function onActionEnd(that) {
        if (_sprite === "Fight") {
            if (that.currentFightOutcome == "lost") {
                _grid.removeNPC(that);
            }
        }
        that.eventType = "default";
        _sprite = "Face";
        _spriteFrame = 0;
        _spriteLength = 1;
        _inAction = false;
        _actionDuration = 10000;
        _actionStart = Date.now();

    }

    this.draw = function(context, sx, sy, w, h) {
        if (Date.now() - _actionStart > _actionDuration) {
            onActionEnd(this);
        }
        var spriteName =  _type.toLowerCase() + _sprite + _facing;
        if (_spriteLength > 1) {
            if (Date.now() - _spriteFrameStart > _spriteFrameDuration) {
                _spriteFrame = (_spriteFrame+1)%_spriteLength;
                spriteName = _type.toLowerCase() + _sprite + _facing + (_spriteFrame+1);
                _spriteFrameDuration = spritesV1[spriteName].duration;
                _spriteFrameStart = Date.now();
            } else {
                spriteName = spriteName + (_spriteFrame+1);
            }
        }
        var sprite = spritesV1[spriteName];
        context.drawImage(NPCsSpritesImage, sprite.x, sprite.y, sprite.w, sprite.h, sx, sy, w, h);
    };

    this.drawDebug = function() {

    };

    this.init = function(args) {
        _x = args.x;
        _y = args.y;
        _type = args.type;
        _id = args.id;
        _grid = args.grid;

        if (args.radius) {
            _radius = args.radius;
        }

        _sprite = "Face";
        _facing = "Down";
        _spriteLength = 1;
        _spriteFrame = 0;
    };

};
